运行机制
# 运行机制
[TOC]
# 一、异步和单线程
因为是单线程,所以必须异步。
# 1.1 异步
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
console.log(3);
console.log(4);
// 一秒后才打印2
// 1 3 4 2
2
3
4
5
6
7
8
9
console.log(1);
setTimeout(function () {
console.log(2);
}, 0);
console.log(3);
console.log(4);
// 1 3 4 2
2
3
4
5
6
7
js 是单线程(同一时间只能做一件事),而且有一个任务队列(**全部的同步任务执行完毕后,再来执行异步任务)。**其中,setTimeout是异步任务。
于是,执行的顺序是:
- 先执行同步任务
console.log(1)
- 遇到异步任务
setTimeout
,要挂起 - 执行同步任务
console.log(3)
- 全部的同步任务执行完毕后,再来执行异步任务
console.log(2)
。
如果同步任务没有执行完,异步任务是不会执行的。
console.log('A');
while (1) {
}
console.log('B');
// A
2
3
4
5
6
while是同步任务,代码会陷入死循环里出不来,自然也就无法打印B
。
console.log('A');
setTimeout(function () {
console.log('B');
})
while (1) {
}
// A
2
3
4
5
6
7
8
9
10
上方代码的打印结果仍然是A
。因为while是同步任务,setTimeout是异步任务,所以还是那句话:如果同步任务没有执行完,队列里的异步任务是不会执行的。
# 1.1.1 异步任务
- setTimeout 和 setInterval
- DOM事件
- Promise
# 1.2 同步
console.log('A');
alert('haha'); //1秒之后点击确认
console.log('B');
2
3
4
5
alert
函数是同步任务,只有点击了确认,才会继续打印B
。
# 1.3 同步和异步的对比
因为setTimeout
是异步任务,所以程序并不会卡在那里,而是继续向下执行(即使setimeout设置了倒计时一万秒);但是alert
函数是同步任务,程序会卡在那里,如果它没有执行,后面的也不会执行(卡在那里,自然也就造成了阻塞)。
# 1.4 使用异步的场景
什么时候需要等待,就什么时候用异步。
- 定时任务:setTimeout(定时炸弹)、setInterval(循环执行)
- 网络请求:ajax请求、动态
<img>
加载 - 事件绑定(比如说,按钮绑定点击事件之后,用户爱点不点。我们不可能卡在按钮那里,什么都不做。所以,应该用异步)
- ES6中的Promise
代码举例:
console.log('start');
var img = document.createElement('img');
// 当页面载入完毕后执行
img.onload = function () {
console.log('loaded');
}
img.src = '/xxx.png';
console.log('end');
2
3
4
5
6
7
8
上图中,先打印start
,然后执行img.src = '/xxx.png'
,然后打印end
,最后打印loaded
。
# 二、任务队列和事件循环
# 2.1 任务队列
# 2.1.1 同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
# 2.1.2 异步任务
不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
- 只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。
# 2.2 Event Loop
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。等到这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等栈中的task执行完之后再去执行任务队列之中的回调函数,回调函数被执行引擎添加到调用栈中。
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 3 3 3
2
3
4
5
6
7
// for是同步任务
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
console.log(i);
// 第 1 个 3 直接输出,1 秒之后,连续输出 3 个 3
2
3
4
5
6
7
8
9